package com.wolfeyes.framework.springsecurity.filter;

import com.wolfeyes.framework.springsecurity.domain.model.CustomizeLoginBody;
import com.wolfeyes.framework.springsecurity.domain.model.CustomizeUserDetails;

import lombok.extern.slf4j.Slf4j;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wolfeyes.common.constant.HttpStatusConstants;
import com.wolfeyes.common.response.ResponseResult;
import com.wolfeyes.common.utils.JwtUtil;
import com.wolfeyes.common.utils.character.StringUtils;
import com.wolfeyes.common.utils.network.web.ResponseUtils;
import com.wolfeyes.framework.service.CustomizeUserDetailsService;
import com.wolfeyes.framework.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @description token校验认证过滤器 验证token有效性
 * @author yanyljava
 * @create 2020-09-04 16:35
 */
@Slf4j
@Component
public class CustomizeJwtAuthenticationTokenFilter extends OncePerRequestFilter {
	
	@Autowired
	private TokenService tokenService;

	@Autowired
	CustomizeUserDetailsService customizeUserDetailsService;
	
	/**
	 * @description 一般而言用户在登录成功后，会主动返回了一个token给前端，那怎么使用这个token呢？
	 * @description 前端发起请求的时候将token放在请求头中，在过滤器中对请求头进行解析：
	 * @description 1.如果有accessToken的请求头（可以自已定义名字），取出token，解析token，解析成功说明token正确，将解析出来的用户信息放到SpringSecurity的上下文中
	 * @description 2.如果有accessToken的请求头，解析token失败（无效token，或者过期失效），取不到用户信息，放行
	 * @description 3.没有accessToken的请求头，放行
	 * @description 这里可能有人会疑惑，为什么token失效都要放行呢？
	 * @description 这是因为SpringSecurity会自己去做登录的认证和权限的校验，靠的就是我们放在SpringSecurity上下文中的
	 * @description SecurityContextHolder.getContext().setAuthentication(authentication);，没有拿到authentication，这里即使放行了，
	 * @description SpringSecurity还是会走到认证和校验授权等逻辑，这个时候SpringSecurity框架就会发现没有登录没有权限。
	 * @description 登陆请求也得经过 customizeJwtAuthenticationTokenFilter，那么可不可以将登陆请求不拦截？
	 * @description 这个过滤器默认是拦截所有请求,对其中的token做解析的, 登录的请求会经过但是因为登录的时候没有token所以不会处理直接放行,没有影响的.
	 * @description 他还是一个filter,参照springboot的filter使用,你也可以配置它过滤哪些url，可灵活处理。
	 * @author yanyljava
	 * @create 2020-09-04 17:45
	 */
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
		
		log.debug("自定义的Token校验认证过滤器被调用 Start！");
		
		//CustomizeLoginBody customizeLoginBody = new ObjectMapper().readValue(request.getInputStream(), CustomizeLoginBody.class);
		//请求头为 accessToken
		//请求体为 Bearer token
		//CustomizeUserDetails customizeUserDetails = tokenService.getCustomizeUserDetails(request);
		//String token = request.getHeader("Token");
		String token = tokenService.getToken(request);
		String username = token != null ?JwtUtil.getUsername(token) : null;
		
//		if (StringUtils.isNotNull(customizeUserDetails)) {
//			
//			// 如果token有效
//			this.tokenService.verifyToken(customizeUserDetails);
//			UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(customizeUserDetails, null, null);
//			authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//			// 在请求过来的时候，过滤器解析请求头中的token，再解析token得到用户信息，再存到SecurityContextHolder中
//			SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//		}
		
		//List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get("authorities"));
		if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
			// 不足之处（可优化）:每次请求都会被OncePerRequestFilter 拦截，每次都会被UserDetailService中的获取用户数据请求数据库
			// 可以考虑做缓存，又或还是用redis或者直接保存内存中-SysUserSessionCacheService-使用redis缓存实现的，存储用户会话session
			CustomizeUserDetails customizeUserDetails = (CustomizeUserDetails) customizeUserDetailsService.loadUserByUsername(username);
			if (customizeUserDetails != null) {
				// 校验Token的有效性
				if (!JwtUtil.verify(token, username, customizeUserDetails.getPassword())) {
					log.error("用户名或密码错误(token无效或者与登录者不匹配)");
					//throw new RuntimeException("用户名或密码错误(token无效或者与登录者不匹配)");
					//ResponseResult responseResult = ResponseResult.error(HttpStatusConstants.FORBIDDEN, "访问受限，授权过期或权限不足");
					ResponseResult responseResult = ResponseResult.error(HttpStatusConstants.FORBIDDEN, "用户名或密码错误(token无效或者与登录者不匹配)");
					//设置response状态码，返回错误信息等
					ResponseUtils.out(responseResult);
					// 直接返回，逻辑中断，不再往下执行
					return ;
				}
				// 将用户信息存入 authentication，方便后续校验
				UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(customizeUserDetails, customizeUserDetails.getPassword(), customizeUserDetails.getAuthorities());
				authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
				// 在请求过来的时候，过滤器解析请求头中的token，再解析token得到用户信息，再存到SecurityContextHolder中
				// 根据token获取用户信息，创建身份认证信息对象UsernamePasswordAuthenticationToken，返回给SpringSecurity框架
				SecurityContextHolder.getContext().setAuthentication(authenticationToken);
			}
		}
		
		// 继续执行下一个过滤器
		chain.doFilter(request, response);
		
//		客户端请求头携带了token，服务端肯定是需要针对每次请求解析、校验token，因此必须定义一个Token过滤器，这个过滤器的主要逻辑如下：
//		1.从请求头中获取accessToken
//		2.对accessToken解析、验签、校验过期时间
//		3.校验成功，将authentication存入authentication中，这样方便后续直接获取用户详细信息。
		log.debug("自定义的Token校验认证过滤器被调用 End！");
		
	}
	
}


